package com.axon.reporting;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRXmlDataSource;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.util.JRLoader;

/**
 * The <code>ReportEngine</code> class serves html and pdf reports using Jasper
 * reports.
 */
public class ReportEngine extends HttpServlet {

  private static final long serialVersionUID = -478711168218312980L;

  private static final String TEMPLATE_EXT = ".jasper";

  private File templatesRoot;

  @Override
  public void init(javax.servlet.ServletConfig config) throws ServletException {
    super.init(config);
    javax.servlet.ServletContext context = config.getServletContext();
    templatesRoot = new File(context.getRealPath("WEB-INF/classes/jasper"));
    log("\n----------------------------\nReports Server inicializado.\n----------------------------");
  }

  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.handleSubmit(request, response);
  }

  @Override
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.handleSubmit(request, response);
  }

  private synchronized void handleSubmit(HttpServletRequest request, HttpServletResponse response) throws IOException {
    PrintWriter out = response.getWriter();
    try {
      Map<String, String> params = parseParameters(request);

      String reports = params.remove("REPORTS");
      String data    = params.remove("DATA");
      String path    = params.remove("PATH");

      generateReport(reports, data, path, params);

      log("\tReport generated successfully");

      out.print("SUCCESS");
    } catch (Exception e) {
      log("\tError generating report", e);
      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      out.print("FAILURE");
    } finally {
      out.flush();
      out.close();
    }
  }

  private void generateReport(String templatePaths, String reportData, String outputPath, Map<String, String> params) throws JRException, IOException {
    File outputFile = new File(outputPath);
    preparePathForFile(outputFile);

    String[] templates = templatePaths.split(";");
    ArrayList<JasperPrint> filledReports = new ArrayList<JasperPrint>(templates.length);
    for (String template : templates) {
      String templatePath = templateFile(template).getAbsolutePath();
      log("\nTemplate:\t" + templatePath);
      filledReports.add(fillReport(templatePath, reportData, params));
    }

    log("\nDestination:\t" + outputFile);
    exportReport(filledReports, outputFile);
  }

  private JasperPrint fillReport(String templatePath, String data, Map<String, String> params) throws JRException, IOException {
    JasperReport jr = (JasperReport) JRLoader.loadObject(templatePath);
    JRXmlDataSource ds = new JRXmlDataSource(new ByteArrayInputStream(data.getBytes("UTF-8")), jr.getQuery().getText());
    return JasperFillManager.fillReport(jr, params, ds);
  }

  private void exportReport(ArrayList<JasperPrint> filledReports, File outputFile) throws JRException {
    JRPdfExporter exporter = new JRPdfExporter();
    exporter.setParameter(JRExporterParameter.JASPER_PRINT_LIST, filledReports);
    exporter.setParameter(JRExporterParameter.OUTPUT_FILE, outputFile);
    exporter.exportReport();
  }

  @SuppressWarnings("unchecked")
  private Map<String, String> parseParameters(HttpServletRequest request) {
    String key, value;
    String[] values;
    Map<String, String> params = new HashMap<String, String>();

    Iterator entries = request.getParameterMap().entrySet().iterator();
    while (entries.hasNext()) {
      Map.Entry<String, String[]> entry = (Map.Entry<String, String[]>) entries.next();
      key = entry.getKey().toUpperCase();
      values = entry.getValue();
      value = (values.length > 1) ? Arrays.toString(values) : values[0]; // FIXME remover colchetes
      params.put(key, value);
    }
    return params;
  }

  private boolean isEmpty(String s) {
    return (s == null || s.trim().equals(""));
  }

  private File templateFile(String template) {
    if (isEmpty(template))
      throw new InvalidParameterException("Template expected.");
    return new File(templatesRoot, template + TEMPLATE_EXT);
  }

  /**
   * Prepares a filesystem path, creating directories as needed
   *
   * @param filePath path to the file to be created
   */
  private boolean preparePathForFile(File filePath) {
    if (filePath == null)
      throw new InvalidParameterException("File Path expected.");
    File targetDir = filePath.getParentFile();
    clearPath(targetDir);
    return targetDir.mkdirs();
  }

  /**
   * Scans a filesystem path and deletes the parts of it which are files
   *
   * @param path Path to be cleared
   */
  private void clearPath(File path) {
    File currentDir = path.getAbsoluteFile();
    while (!currentDir.isDirectory()) {
      if (currentDir.isFile())
        currentDir.delete();
      currentDir = currentDir.getParentFile();
    }
  }

}

